package cn.xydpeng.springframework.aop.support.annotation;

import cn.xydpeng.springframework.aop.ClassFilter;
import cn.xydpeng.springframework.util.Assert;

import java.lang.annotation.Annotation;
import java.lang.annotation.Native;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * @author xydpeng
 * @version v1.0
 * @date: 2022/10/22
 * @description:
 */
public class AnnotationClassFilter implements ClassFilter {
    private final Class<? extends Annotation> annotationType;
    private final boolean checkInherited;

    /**
     * 注意：checkInherited 是否要检查目标类的父类、接口，以及注解上的元注解（组合注解）
     */
    public AnnotationClassFilter(Class<? extends Annotation> annotationType, boolean checkInherited) {
        Assert.notNull(annotationType, "annotationType must not be null");
        this.annotationType = annotationType;
        this.checkInherited = checkInherited;
    }

    @Override
    public boolean matches(Class<?> clazz) {
        if (!this.checkInherited) {
            //注解上有元注解@Inherited时,子类可以继承到父类上的注解
            return clazz.isAnnotationPresent(this.annotationType);
        }
        Set<Annotation> allAnnotations = new HashSet<>();
        allAnnotations.addAll(Arrays.asList(clazz.getAnnotations()));
        //需要检查接口、以及注解上的元注解
        //1、目标类实现的接口
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> interfacez : interfaces) {
            if (interfacez.isAnnotationPresent(this.annotationType)) return true;
            allAnnotations.addAll(Arrays.asList(interfacez.getAnnotations()));
        }
        //2、注解上的元注解（组合注解）的情况
        boolean javaNativeTarget = this.annotationType.getName().startsWith("java.");
        Set<Annotation> needCheckAnnotations = allAnnotations;
        while (needCheckAnnotations != null && needCheckAnnotations.size() > 0) {

            Set<Annotation> nextSet = new HashSet<>();
            for (Annotation annotation : needCheckAnnotations) {
                if (annotation.annotationType().equals(this.annotationType)) return true;
                Class<? extends Annotation> aClazz = annotation.annotationType();
                if (aClazz.isAnnotationPresent(this.annotationType)) return true;
                boolean javaNativeSource = aClazz.getName().startsWith("java.");
                if (!javaNativeTarget && javaNativeSource) continue;
                nextSet.addAll(Arrays.asList(aClazz.getAnnotations()));
            }
            needCheckAnnotations = nextSet;
        }
        return false;
    }

}
